# uncompyle6 version 3.2.3
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.6.8 |Anaconda custom (64-bit)| (default, Feb 21 2019, 18:30:04) [MSC v.1916 64 bit (AMD64)]
# Embedded file name: site-packages\Crypto\Random\Fortuna\FortunaAccumulator.py
__revision__ = "$Id$"
import sys

if sys.version_info[0] == 2:
    if sys.version_info[1] == 1:
        from Crypto.Util.py21compat import *
from Crypto.Util.py3compat import *
from binascii import b2a_hex
import time, warnings
from Crypto.pct_warnings import ClockRewindWarning
from . import SHAd256
from . import FortunaGenerator


class FortunaPool(object):
    """Fortuna pool type
    
    This object acts like a hash object, with the following differences:
    
        - It keeps a count (the .length attribute) of the number of bytes that
          have been added to the pool
        - It supports a .reset() method for in-place reinitialization
        - The method to add bytes to the pool is .append(), not .update().
    """

    digest_size = SHAd256.digest_size

    def __init__(self):
        self.reset()

    def append(self, data):
        self._h.update(data)
        self.length += len(data)

    def digest(self):
        return self._h.digest()

    def hexdigest(self):
        if sys.version_info[0] == 2:
            return b2a_hex(self.digest())
        else:
            return b2a_hex(self.digest()).decode()

    def reset(self):
        self._h = SHAd256.new()
        self.length = 0


def which_pools(r):
    """Return a list of pools indexes (in range(32)) that are to be included during reseed number r.
    
    According to _Practical Cryptography_, chapter 10.5.2 "Pools":
    
        "Pool P_i is included if 2**i is a divisor of r.  Thus P_0 is used
        every reseed, P_1 every other reseed, P_2 every fourth reseed, etc."
    """
    if not r >= 1:
        raise AssertionError
    retval = []
    mask = 0
    for i in range(32):
        if r & mask == 0:
            retval.append(i)
        else:
            break
        mask = mask << 1 | 1

    return retval


class FortunaAccumulator(object):
    min_pool_size = 64
    reseed_interval = 0.1

    def __init__(self):
        self.reseed_count = 0
        self.generator = FortunaGenerator.AESGenerator()
        self.last_reseed = None
        self.pools = [FortunaPool() for i in range(32)]
        if not self.pools[0] is not self.pools[1]:
            raise AssertionError

    def _forget_last_reseed(self):
        self.last_reseed = None

    def random_data(self, bytes):
        current_time = time.time()
        if self.last_reseed is not None:
            if self.last_reseed > current_time:
                warnings.warn(
                    "Clock rewind detected. Resetting last_reseed.", ClockRewindWarning
                )
                self.last_reseed = None
        if self.pools[0].length >= self.min_pool_size:
            if (
                self.last_reseed is None
                or current_time > self.last_reseed + self.reseed_interval
            ):
                self._reseed(current_time)
        return self.generator.pseudo_random_data(bytes)

    def _reseed(self, current_time=None):
        if current_time is None:
            current_time = time.time()
        seed = []
        self.reseed_count += 1
        self.last_reseed = current_time
        for i in which_pools(self.reseed_count):
            seed.append(self.pools[i].digest())
            self.pools[i].reset()

        seed = b("").join(seed)
        self.generator.reseed(seed)

    def add_random_event(self, source_number, pool_number, data):
        if not 1 <= len(data) <= 32:
            raise AssertionError
        if not 0 <= source_number <= 255:
            raise AssertionError
        if not 0 <= pool_number <= 31:
            raise AssertionError
        self.pools[pool_number].append(bchr(source_number))
        self.pools[pool_number].append(bchr(len(data)))
        self.pools[pool_number].append(data)
